package support

import (
	"database/sql"
	"database/sql/driver"
	"gitee.com/gcom/gbox/errors"
	"net/http"
	"strconv"
	"time"
)

type duration time.Duration

type sessionRecord struct {
	Id        uint64 `gorm:"primary_key"`
	UserId    int64
	Role      string
	Data      string
	Expire    duration
	Discard   bool
	CreateIp  string
	LastIp    string
	CreatedAt time.Time
	ActiveAt  time.Time
	ExpireAt  time.Time
}

type mysqlStore struct{}

var sessionStore = mysqlStore{}

func (s mysqlStore) Get(ctx TracerCtx) (*Session, error) {
	if ctx.Req == nil {
		return nil, errors.New("非WEB请求")
	}

	var id uint64
	var sid string
	if c, _ := ctx.Req.Cookie(SessionCookieKey); c != nil {
		sid = c.Value
	} else if h := ctx.Req.Header.Get(SessionHeaderKey); h != "" {
		sid = h
	}
	if sid != "" {
		if _id, e := strconv.ParseUint(sid, 10, 64); e == nil {
			id = _id
		} else {
			ctx.Logger.Infof("无效的sid: %s", sid)
		}
	}

	if id > 0 {
		if s := getSession(ctx, id); s != nil {
			return s, nil
		}
		ctx.Logger.Infof("无效的sid: %s", sid)
	}
	return nil, nil
}

func (s mysqlStore) Save(se *Session) error {
	if se.isNew {
		return insertSession(se)
	} else {
		return updateSession(se)
	}
}

func (s mysqlStore) Discard(se *Session) {
	se.discard = true
	se.ctx.Resp.Header().Set(SessionHeaderKey, "")
	cookie := &http.Cookie{
		Name:     SessionCookieKey,
		Value:    "",
		Path:     "/",
		Domain:   "",
		MaxAge:   0,
		Secure:   false,
		HttpOnly: false,
		SameSite: http.SameSiteLaxMode,
	}
	http.SetCookie(se.ctx.Resp, cookie)
}

func (sessionRecord) TableName() string {
	return "sys_session"
}

func (d duration) Value() (driver.Value, error) {
	return int64(d) / int64(time.Second), nil
}

func (d *duration) Scan(src interface{}) error {
	s := sql.NullInt64{}
	err := s.Scan(src)
	if err != nil {
		return errors.Wrap(err, "无法解析的超时时长类型")
	}
	*d = duration(s.Int64 * int64(time.Second))
	return nil
}

func getSession(ctx TracerCtx, id uint64) *Session {
	var rec = &sessionRecord{}
	err := ctx.DB.First(rec, id).Error
	if err != nil || rec.Discard || rec.ExpireAt.Before(time.Now()) {
		return nil
	}
	return &Session{
		ID:       rec.Id,
		UserId:   rec.UserId,
		Role:     rec.Role,
		Expire:   time.Duration(rec.Expire),
		CreateIp: rec.CreateIp,
		LastIp:   rec.LastIp,
		Values:   nil,
		isNew:    false,
		ctx:      ctx,
	}
}

func insertSession(s *Session) error {
	var now = time.Now()
	var expire = now.Add(s.Expire)
	var rec = &sessionRecord{
		Id:        s.ID,
		UserId:    s.UserId,
		Role:      s.Role,
		Expire:    duration(s.Expire),
		Discard:   s.discard,
		Data:      "",
		CreateIp:  s.CreateIp,
		LastIp:    s.LastIp,
		CreatedAt: now,
		ActiveAt:  now,
		ExpireAt:  expire,
	}
	return s.ctx.DB.Create(rec).Error
}

func updateSession(s *Session) error {
	var now = time.Now()
	var expire = time.Now().Add(s.Expire)
	var rec = &sessionRecord{
		Id:       s.ID,
		UserId:   s.UserId,
		Role:     s.Role,
		Expire:   duration(s.Expire),
		Discard:  s.discard,
		Data:     "",
		LastIp:   s.LastIp,
		ActiveAt: now,
		ExpireAt: expire,
	}

	db := s.ctx.DB.Model(rec).Updates(rec)
	if db.Error != nil {
		return db.Error
	}
	//if db.RowsAffected == 0 {
	//	return errors.New("无效sessionId: " + strconv.FormatUint(s.ID, 10))
	//}
	return nil
}
